package net.qiqbframework.spring.config;


import net.qiqbframework.common.Priority;
import net.qiqbframework.messaging.Message;
import net.qiqbframework.messaging.annotation.HandlerParameterResolver;
import net.qiqbframework.messaging.annotation.HandlerParameterResolverFactory;
import net.qiqbframework.common.utils.AnnotationUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.lang.NonNull;

import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Optional;

/**
 * ParameterResolverFactory implementation that resolves parameters using Spring dependency resolution. Mark a parameter
 * as {@link Autowired} to resolve said parameter using Spring dependency
 * resolution.
 *
 * @see Autowired
 */
@Priority(Priority.HIGH)
public class SpringBeanDependencyResolverFactory implements HandlerParameterResolverFactory {

    private final ApplicationContext applicationContext;


    public SpringBeanDependencyResolverFactory(@NonNull ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public HandlerParameterResolver<?> createInstance(Executable executable, Parameter[] parameters, int parameterIndex) {
        final Optional<Boolean> ann =
                AnnotationUtil.findAnnotationAttribute(parameters[parameterIndex], Autowired.class, "required");

        if (!ann.isPresent()) {
            return null;
        }

        final boolean required = ann.get();
        final MethodParameter methodParameter;

        if (executable instanceof Method) {
            methodParameter = new MethodParameter((Method) executable, parameterIndex);
        } else {
            methodParameter = new MethodParameter((Constructor<?>) executable, parameterIndex);
        }

        final DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(methodParameter, required);
        return new SpringBeanDependencyResolver(
                applicationContext.getAutowireCapableBeanFactory(), dependencyDescriptor
        );
    }

    private static class SpringBeanDependencyResolver implements HandlerParameterResolver<Object> {

        private final AutowireCapableBeanFactory beanFactory;
        private final DependencyDescriptor dependencyDescriptor;

        public SpringBeanDependencyResolver(AutowireCapableBeanFactory beanFactory,
                                            DependencyDescriptor dependencyDescriptor) {
            this.beanFactory = beanFactory;
            this.dependencyDescriptor = dependencyDescriptor;
        }

        @Override
        public Object resolveParameter(Message<?> message) {
            return beanFactory.resolveDependency(dependencyDescriptor, null);
        }

        @Override
        public boolean support(Message<?> message) {
            return true;
        }
    }
}
